package com.hzw.saas.service.hpc.service.impl;

import cn.hutool.core.date.DateUtil;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.IService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.hzw.saas.api.hpc.*;
import com.hzw.saas.api.hpc.bo.*;
import com.hzw.saas.api.hpc.enums.HpcAccountPoolStatus;
import com.hzw.saas.api.hpc.enums.HpcAccountRecordStatus;
import com.hzw.saas.api.hpc.enums.HpcAccountStatus;
import com.hzw.saas.api.hpc.query.HpcAccountRecordQuery;
import com.hzw.saas.api.hpc.query.HpcAccountRenewalQuery;
import com.hzw.saas.common.config.annotation.RedisProcessLock;
import com.hzw.saas.common.config.util.AssertUtil;
import com.hzw.saas.common.util.Arith;
import com.hzw.saas.common.util.MapperUtil;
import com.hzw.saas.common.util.SnowFlakeUtil;
import com.hzw.saas.service.hpc.event.HpcAccountAllotEvent;
import com.hzw.saas.service.hpc.event.HpcAccountUnAllotEvent;
import com.hzw.saas.service.hpc.mapper.HpcAccountRecordMapper;
import com.hzw.saas.service.hpc.model.HpcAccountAmountDaily;
import com.hzw.saas.service.hpc.model.HpcAccountRecord;
import com.hzw.saas.service.hpc.model.HpcBalanceOrder;
import jodd.util.StringUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import ma.glasnost.orika.MapperFacade;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;

/**
 * <p>
 *
 * </p>
 *
 * @author sonam
 * @since 2021/5/11 15:12
 */
@Slf4j
@RequiredArgsConstructor
@Service("hpcAccountRecordServiceImpl")
public class HpcAccountRecordServiceImpl extends ServiceImpl<HpcAccountRecordMapper, HpcAccountRecord> implements IService<HpcAccountRecord>, IHpcAccountRecordService {
    @Resource(name = "hpcAccountRecordServiceImpl")
    private IHpcAccountRecordService self;
    @Resource(name = "hpcAccountServiceImpl")
    private IHpcAccountService hpcAccountService;
    @Resource(name = "hpcAccountUserServiceImpl")
    private IHpcAccountUserService hpcAccountUserService;
    @Resource(name = "hpcAccountIncreaseImpl")
    private IHpcAccountIncreaseService hpcAccountIncreaseService;
    @Resource(name = "sshHpcFetchServiceImpl")
    private IHpcFetchService hpcFetchService;
    @Resource(name = "hpcBalanceOrderServiceImpl")
    private IHpcBalanceOrderService hpcBalanceOrderService;
    @Resource(name = "hpcAccountDetailServiceImpl")
    private IHpcAccountDetailService hpcAccountDetailService;
    @Resource(name = "hpcAccountAmountDailyServiceImpl")
    private IHpcAccountAmountDailyService hpcAccountAmountDailyService;

    private final MapperFacade mapperFacade;
    private final HpcAccountRecordMapper recordMapper;
    private final ApplicationEventPublisher publisher;

    @Override
    public List<HpcAccountRecordBo> getList() {
        return mapperFacade.mapAsList(this.list(), HpcAccountRecordBo.class);
    }

    @Override
    public HpcAccountRecordBo get(String pid) {
        if(StringUtil.isBlank(pid)) return null;

        HpcAccountRecord hpcAccountRecord = this.getById(pid);
        return mapperFacade.map(hpcAccountRecord, HpcAccountRecordBo.class);
    }

    @Override
    @Transactional
    public HpcAccountRecordBo add(@Validated HpcAccountRecordQuery accountRecordQuery) {
        HpcAccountRecordBo accountRecordBo = mapperFacade.map(accountRecordQuery, HpcAccountRecordBo.class);

        String pid = accountRecordBo.getPid();
        if (StringUtil.isBlank(pid)) {
            accountRecordBo.setPid(String.valueOf(SnowFlakeUtil.nextId()));
        }

        // 检查HPC账号状态
        String hpcAccountId = accountRecordBo.getHpcAccountId();
        HpcAccountPoolStatus hpcAccountStatus = self.getHpcAccountStatus(hpcAccountId);
        AssertUtil.assertThrow("HPC账号已被占用", Objects.equals(hpcAccountStatus, HpcAccountPoolStatus.BE_OCCUPIED));

        HpcAccountBo hpcAccount = hpcAccountService.getById(hpcAccountId);
        AssertUtil.assertThrow("请设置正确的HPC账号", Objects.isNull(hpcAccount));

        // 检查过期时间是否有效
        Date expireTime = accountRecordBo.getExpireTime();
        AssertUtil.assertThrow("请设置正确的有效日期", (Objects.isNull(expireTime) || DateUtil.compare(expireTime, DateUtil.date()) < 0));

        accountRecordBo.setHpcAccountSnapshot(hpcAccount.getHpcAccount()); // 设置HPC账号名
        this.save(mapperFacade.map(accountRecordBo, HpcAccountRecord.class));

        // 分配账号提交事件
        publisher.publishEvent(new HpcAccountAllotEvent(hpcAccount));

        return accountRecordBo;
    }

    @Override
    public List<String> listByLikeHpcAccountName(String hpcAccount) {
        if(StringUtil.isBlank(hpcAccount)) return new ArrayList<>();
        List<HpcAccountRecord> hpcAccountRecords = this.list(Wrappers.<HpcAccountRecord>lambdaQuery()
            .select(HpcAccountRecord::getPid)
            .like(HpcAccountRecord::getHpcAccountSnapshot, hpcAccount));
        return hpcAccountRecords.stream().map(HpcAccountRecord::getPid).collect(Collectors.toList());
    }

    @Override
    public HpcAccountPoolStatus getHpcAccountStatus(String hpcAccountId) {
        return this.count(Wrappers.<HpcAccountRecord>lambdaQuery().ne(HpcAccountRecord::getStatus, HpcAccountRecordStatus.UNBOUND.value()).eq(HpcAccountRecord::getHpcAccountId, hpcAccountId)) > 0 ? HpcAccountPoolStatus.BE_OCCUPIED : HpcAccountPoolStatus.FREE;
    }

    @Override
    @Transactional
    @RedisProcessLock(lockName = "RenewHpcAccountUser", key = "#hpcAccountRenewalQuery.pid")
    public void renewHpcAccountUser(@Validated HpcAccountRenewalQuery hpcAccountRenewalQuery) {
        String pid = hpcAccountRenewalQuery.getPid(); // HPC-RECORD-ID或者HPC-USER-ID
        Double coreTimeTotal = hpcAccountRenewalQuery.getCoreTimeTotal(); // HPC参数总核时
        Double coreTime = hpcAccountRenewalQuery.getCoreTime(); // 续期核时
        Double giveCoreTime = hpcAccountRenewalQuery.getGiveCoreTime(); // 赠送核时
        Date expireDate = hpcAccountRenewalQuery.getExpireDate(); // 过期时间
        Date busDate = hpcAccountRenewalQuery.getBusDate(); // 登记日期
        int isInfinite = 0; // 是否无限算力
        if(StringUtil.isNotBlank(hpcAccountRenewalQuery.getIsInfinite())) {
            isInfinite = Integer.parseInt(hpcAccountRenewalQuery.getIsInfinite());
        }

        HpcAccountUserBo hpcAccountUserBo = hpcAccountUserService.get(pid);
        if(hpcAccountUserBo == null) {
            hpcAccountUserBo = hpcAccountUserService.getMainUserByRecordId(pid);
        }
        AssertUtil.assertThrow("续费失败，该Hpc帐号不存在", Objects.isNull(hpcAccountUserBo));

        HpcAccountRecordBo hpcAccountRecordBo = self.get(hpcAccountUserBo.getHpcAccountRecordId());
        AssertUtil.assertThrow("续费失败，该Hpc帐号不存在", Objects.isNull(hpcAccountRecordBo));
        String hpcAccountRecordId = hpcAccountRecordBo.getPid();
        // 已用核时
        double usableCoreTime = Arith.sub(hpcAccountRecordBo.getCoreTimeBalanceTotal(), hpcAccountRecordBo.getCoreTimeBalance());

        if(coreTimeTotal != null) {
            // 当前Hpc账号总核时
            coreTimeTotal = Arith.add(coreTimeTotal, coreTime); // 充值之后的金额
        } else {
            coreTimeTotal = Arith.add(hpcAccountRecordBo.getCoreTimeBalanceTotal(), coreTime);
        }

        HpcAccountRecord accountRecord = new HpcAccountRecord();
        accountRecord.setPid(hpcAccountRecordId);
        accountRecord.setCoreTimeBalance(Arith.sub(coreTimeTotal, usableCoreTime));
        accountRecord.setCoreTimeBalanceTotal(coreTimeTotal);
        accountRecord.setExpireTime(DateUtil.offsetSecond(DateUtil.endOfDay(expireDate), -1));
        accountRecord.setStatus(HpcAccountRecordStatus.NORMAL.value());
        if(Objects.nonNull(busDate)) {
            accountRecord.setCreateTime(busDate);
        }
        this.updateById(accountRecord);

        // 记录续费
        HpcBalanceOrder balanceOrder = new HpcBalanceOrder();
        balanceOrder.setPid(SnowFlakeUtil.nextIdStr());
        balanceOrder.setHpcAccountRecordId(hpcAccountRecordId);
        balanceOrder.setGiveCoreTime(giveCoreTime);
        balanceOrder.setCoreTime(coreTime);
        balanceOrder.setLastCoreTime(hpcAccountRecordBo.getCoreTimeBalanceTotal());
        hpcBalanceOrderService.insert(mapperFacade.map(balanceOrder, HpcBalanceOrderBo.class));

        // 如果账号冻结则解冻
        if(!Objects.equals(isInfinite, hpcAccountUserBo.getIsInfinite()) ||
            !Objects.equals(hpcAccountUserBo.getStatus(), HpcAccountStatus.NORMAL.value())) {
            hpcAccountUserBo.setIsInfinite(isInfinite);
            hpcAccountUserBo.setStatus(HpcAccountStatus.NORMAL.value());
            hpcAccountUserService.update(hpcAccountUserBo);
            // 提交分配HPC账号事件
            publisher.publishEvent(new HpcAccountAllotEvent(hpcAccountService.getById(hpcAccountRecordBo.getHpcAccountId())));
        }
    }

    @Override
    public HpcAccountRecordBo searchOneByHpcAccountId(String hpcAccountId) {
        if (StringUtil.isBlank(hpcAccountId)) return null;

        List<HpcAccountRecord> hpcAccountRecords = this.list(Wrappers.<HpcAccountRecord>lambdaQuery()
            .eq(HpcAccountRecord::getStatus, 1)
            .eq(HpcAccountRecord::getHpcAccountId, hpcAccountId)
            .orderByDesc(HpcAccountRecord::getCreateTime));
        if (CollectionUtils.isEmpty(hpcAccountRecords)) {
            return null;
        }

        return mapperFacade.map(hpcAccountRecords.get(0), HpcAccountRecordBo.class);
    }

    @Override
    @Transactional
    public void unBoundHpcAccountRecord(String hpcAccountRecordId) {
        HpcAccountRecordBo accountUserBo = self.get(hpcAccountRecordId);

        HpcAccountRecord hpcAccountRecord = new HpcAccountRecord();
        hpcAccountRecord.setPid(hpcAccountRecordId);
        if (DateUtil.compare(accountUserBo.getExpireTime(), DateUtil.date()) > 0) {
            hpcAccountRecord.setExpireTime(DateUtil.date());
        }
        hpcAccountRecord.setStatus(HpcAccountRecordStatus.UNBOUND.value());
        // 更新
        this.updateById(hpcAccountRecord);
        // 删除
        this.removeById(hpcAccountRecordId);
        // 删除hpcAccountUser数据
        hpcAccountUserService.deleteByRecordId(hpcAccountRecordId);
        // 提交移除Slurm用户事件
        publisher.publishEvent(new HpcAccountUnAllotEvent(hpcAccountService.getById(accountUserBo.getHpcAccountId())));
    }

    @Override
    public void update(HpcAccountRecordBo hpcAccountRecordBo) {
        HpcAccountRecord hpcAccountRecord = mapperFacade.map(hpcAccountRecordBo, HpcAccountRecord.class);
        this.updateById(hpcAccountRecord);
    }

    @Override
    public HpcAccountAmountDailyBo recordHpcBalanceByDate(String recordId, Date date) {
        if (StringUtil.isBlank(recordId)) {
            log.warn("recordId不能为空");
            return null;
        }
        if (Objects.isNull(date)) {
            log.warn("记录的日期不能为空");
            return null;
        }

        HpcAccountRecord hpcAccountRecord = this.baseMapper.getHpcAccountRecordByPid(recordId);
        if(Objects.isNull(hpcAccountRecord)) {
            log.warn("记录失败，当前HPC-RECORD账户不存在: {}", recordId);
            return null;
        }

        Date busDate = DateUtil.beginOfDay(date);
        HpcAccountAmountDaily accountAmountDaily = new HpcAccountAmountDaily();
        accountAmountDaily.setPid(SnowFlakeUtil.nextIdStr());
        accountAmountDaily.setHpcAccountRecordId(recordId);
        accountAmountDaily.setBusTime(busDate);
        accountAmountDaily.setUpdateTime(new Date());
        accountAmountDaily.setCreateTime(new Date());
        accountAmountDaily.setIsDelete(0);

        AssertUtil.assertThrow("记录失败，记录得日期必须小于今日日期", DateUtil.compare(accountAmountDaily.getBusTime(), DateUtil.beginOfDay(new Date())) >= 0);

        HpcAccountAmountDailyBo accountAmountDailyBo = hpcAccountAmountDailyService.getOne(new HpcAccountAmountDailyBo().setHpcAccountRecordId(recordId).setBusTime(accountAmountDaily.getBusTime()));

        // sum
        Double sumCoreTime = hpcAccountDetailService.getHpcAccountCoreTimeSum(recordId);
        // date ~ now
        Double sumCoreTime1 = hpcAccountDetailService.getHpcCoreSumByScopeDate(Collections.singletonList(recordId), DateUtil.beginOfDay(DateUtil.offsetDay(busDate, 1)), DateUtil.date());
        // last ~ date
        sumCoreTime = Arith.sub(sumCoreTime, sumCoreTime1);

        double curBalance = Arith.sub(hpcAccountRecord.getCoreTimeBalanceTotal(), sumCoreTime);

        if (accountAmountDailyBo != null) {
            // 存在则比余额进行更新
            if (!Objects.equals(curBalance, accountAmountDailyBo.getCurCoreTimeBalance())) {
                accountAmountDailyBo.setUpdateTime(new Date());
                accountAmountDailyBo.setCurCoreTimeBalance(curBalance);
                hpcAccountAmountDailyService.update(accountAmountDailyBo);
            }
            return accountAmountDailyBo;
        } else {
            accountAmountDaily.setCurCoreTimeBalance(curBalance);
            return hpcAccountAmountDailyService.save(mapperFacade.map(accountAmountDaily, HpcAccountAmountDailyBo.class));
        }
    }

    @Override
    public List<String> getRecordIdsByExpire() {
        List<HpcAccountRecord> list = this.lambdaQuery().lt(HpcAccountRecord::getExpireTime, LocalDateTime.now()).eq(HpcAccountRecord::getStatus, 1).select(HpcAccountRecord::getPid).list();
        return list.stream().map(HpcAccountRecord::getPid).collect(Collectors.toList());
    }

    @Override
    public List<UserAndAccountBo> findNeedIncrease() {
        List<HpcAccountRecordBo> recordPidNeedIncrease = listUsableHpcAccountRecordInfo();
        List<UserAndAccountBo> userAndAccountBos = mapperFacade.mapAsList(recordPidNeedIncrease, UserAndAccountBo.class);
        List<String> alreadyIncreased = hpcAccountIncreaseService.findAlreadyIncreased();
        ArrayList<UserAndAccountBo> needDelete = new ArrayList<>();
        userAndAccountBos.forEach(userAndAccountBo ->
            {
                for (String s : alreadyIncreased) {
                    if (userAndAccountBo.getPid().equals(s)) {
                        needDelete.add(userAndAccountBo);
                    }
                }
            }
        );
        if (CollectionUtils.isNotEmpty(needDelete)) {
            userAndAccountBos.removeAll(needDelete);
        }
        List<String> collect = userAndAccountBos.stream().map(UserAndAccountBo::getPid).collect(Collectors.toList());
        if (CollectionUtils.isNotEmpty(collect)) {
            Map<String, BigDecimal> hpcAccountAmountDailyBo = hpcAccountAmountDailyService.findHpcAccountAmountDailyBo(collect);
            if (!hpcAccountAmountDailyBo.isEmpty()) {
                userAndAccountBos.forEach(userAndAccountBo -> {
                    userAndAccountBo.setCoreTimeBalance(BigDecimal.ZERO);
                    hpcAccountAmountDailyBo.forEach((s, bigDecimal) -> {
                        if (userAndAccountBo.getPid().equals(s)) {
                            userAndAccountBo.setCoreTimeBalance(bigDecimal);
                        }
                    });
                });
            }
            //修复bug，账号换人的id不进增值
            return userAndAccountBos;
        }
        return null;

    }

    @Override
    public List<HpcAccountUserInfoBo> findNeedRepair(List<String> accountId) {
        List<HpcAccountRecord> list = this.lambdaQuery().in(HpcAccountRecord::getHpcAccountId, accountId).orderByAsc(HpcAccountRecord::getCreateTime).list();
        return MapperUtil.nf().mapAsList(list, HpcAccountUserInfoBo.class);
    }

    @Override
    public void refreshHpcAccount(String hpcAccountRecordId) {
        HpcAccountRecordBo hpcAccountRecordBo = self.get(hpcAccountRecordId);
        // 拉取明细
        hpcFetchService.fetchHpcDetails(hpcAccountRecordBo);
        // 更新余额
        hpcFetchService.updateHpcAccountBalance(hpcAccountRecordBo);
    }

    @Override
    public List<HpcAccountRecordBo> listUsableHpcAccountRecordInfo() {
        // 查询record表中前一天这个时间之后更新的且状态为正常的去做增值
        return recordMapper.listNeedIncreaseHpcRecordList(DateUtil.date());
    }

    @Override
    public List<HpcAccountRecordBo> getListByHpcConfigName(String hpcConfigName) {
        return recordMapper.getListByHpcConfigName(hpcConfigName);
    }

    @Override
    public List<HpcAccountRecordBo> getInvalidHpcAccount() {
        return MapperUtil.nf().mapAsList(this.lambdaQuery().le(HpcAccountRecord::getCoreTimeBalanceTotal, 0).or()
            .lt(HpcAccountRecord::getExpireTime, new Date()).list(), HpcAccountRecordBo.class);
    }

    @Override
    public List<HpcAccountRecordBo> listRecordByProviderType(String providerType) {
        return super.baseMapper.listRecordByProviderType(providerType);
    }

    @Override
    public List<HpcAccountBaseBO> listHpcAccountsByUser(String userId, String providerType) {
        return super.baseMapper.listHpcAccountsByUserId(userId, providerType);
    }
}
